//
//  PowerAllocator.cpp
//  MemoryManager
//
//  Created by Bram Buurlage on 6/28/12.
//  Copyright (c) 2012 Bram Buurlage. All rights reserved.
//

#include <iostream>
#include <cmath>
#include <cassert>
#include "BuddyAllocator.h"

BuddyAllocator::BuddyAllocator(): Allocator("Buddy system")
{
    baselog = std::log(2.0);
    headers = 0x0;
}

BuddyAllocator::~BuddyAllocator()
{
}

void BuddyAllocator::printFragmentation()
{
    int i=0;
    for (int x=minpower; x<=maxpower; ++x)
    {
        headers[i++]->printFragmentation();
    }
}

const Area* BuddyAllocator::alloc(int wanted)
{
    wanted += sizeof(void*);
    if (wanted > 1024) return 0x0;
    
    assert(wanted <= (1 << maxpower));
    int bufsize = (1 << minpower);
    
    int index = 0;
    while (bufsize < wanted) 
    {
        index++;
        bufsize <<= 1;
    }
    
    return headers[index]->alloc();
}

void BuddyAllocator::free(const Area* area)
{
    PowerOfTwoArea* a = (PowerOfTwoArea*)area;
    ((BuddyListHeader*)a->header)->free(a);
}

void BuddyAllocator::setSize(int size)
{
    Allocator::setSize(size);
    
    maxpower = (int)(std::log((double)size) / baselog);
    minpower = 4;
    
    std::cout << minpower << "," << maxpower << std::endl;
    
    delete[] headers;
    
    int nheaders = maxpower - minpower;
    headers = new BuddyListHeader*[nheaders];
    
    int i=0;
    for (int p=minpower; p<=maxpower; ++p)
    {
        int size = 1 << p;
        headers[i] = new BuddyListHeader(size);
        if (i > 0)
        {
            headers[i-1]->setHighLevel(headers[i]);
        }
        i += 1;
    }
    
    headers[i-1]->addNewArea(new PowerOfTwoArea(0, size));
}

BuddyListHeader::BuddyListHeader(int size)
{
    this->size = size;
    
    firstFreeArea = 0x0;
    higherLevel = 0x0;
}

BuddyListHeader::~BuddyListHeader()
{
    PowerOfTwoArea* current = firstFreeArea;
    while (current != 0x0)
    {
        PowerOfTwoArea* next = (PowerOfTwoArea*)current->header;
        delete current;
        current = next;
    }
}

void BuddyListHeader::setHighLevel(BuddyListHeader *level)
{
    higherLevel = level;
}

void BuddyListHeader::addNewArea(PowerOfTwoArea* a)
{
    if (firstFreeArea)
    {
        a->header = (void*)firstFreeArea;
    }
    else
    {
        a->header = 0x0;
    }
    
    firstFreeArea = a;
}

Area* BuddyListHeader::alloc()
{
    PowerOfTwoArea* freeArea = firstFreeArea;
    if (freeArea)
    {
        firstFreeArea = (PowerOfTwoArea*)freeArea->header;
        freeArea->header = (void*)this;
        
        return freeArea;
    }
    else if (higherLevel)
    {
        Area* area = higherLevel->alloc();
        if (area)
        {
            int real_size = area->getSize() + sizeof(void*);
            int real_base = area->getBase() - sizeof(void*);
            delete area;
            
            PowerOfTwoArea* area_a = new PowerOfTwoArea(real_base, real_size/2);
            PowerOfTwoArea* area_b = new PowerOfTwoArea(real_base+real_size/2, real_size/2);
            addNewArea(area_b);
            
            area_a->header = (void*)this;
            return area_a;
        }
    }
    
    return 0x0;
}

void BuddyListHeader::free(PowerOfTwoArea* a)
{
    if (a->header == (void*)this)
    {
        if (firstFreeArea)
        {
            a->header = (void*)firstFreeArea;
        }
        else
        {
            a->header = 0x0;
        }
        
        firstFreeArea = a;
    }
}

void BuddyListHeader::printFragmentation()
{
    std::cout << "ListHeader " << size << ": ";
    int amount = 0;
    
    PowerOfTwoArea* area = firstFreeArea;
    while (area != 0x0)
    {
        amount++;
        area = (PowerOfTwoArea*)area->header;
    }
    
    std::cout << amount << " gebieden vrij" << std::endl;
}